package bosVM2.FaultHandling;


public class StringEncoderDecoder {

	int byteLength = 16;
	String encoding = "UTF-16";
	//Hamming codes encoding of a string
	public String enCode(String s) {
		char[] block = getBitPattern(s,false);
		//retrieve the bit pattern
		int n = block.length;

		//calculate the needed amount of parity bits
		int parityBits = calculateEncodingParityBits(n);
		//create message
		char[] message = new char[n+parityBits];
		

		int nextPower = 4 ,powerIndex = 2;
		int blocki = 0;

		for(int i = 0; i < message.length; i++) {
			if(i+1 == nextPower) {
				message[i] = '0';
				powerIndex++;
				nextPower = (int) Math.pow(2, powerIndex);
			} else if ( i < 2 ) { 
				message[i] = '0';
			}
			else{
				message[i] = block[blocki];
				blocki++;
			}		
		}
		
		//calculate each parity bit
		for(int bit = 0; bit < parityBits; bit++) {
			int skip = (int) Math.pow(2, bit);
			int val = 0;
			for(int i = skip-1; i < message.length; i = i + skip) {
				for(int j = 0; j < skip && i < message.length; j++) {
					val = val + Character.getNumericValue(message[i]);
					i++;
				}
			}
			
			//Write the parity bit
			if(val % 2 == 0) {
				message[skip-1] = '0';
			} else { message[skip-1] = '1';}
		}		
		return toASCII(message);
	}

	//Calculates the parity values of an encoded string
	//@return Return the parity value, aka. the bit in error(power of 2)
	public int calculateParityValues(int parityBits, char[] message) {
		int out = 0;
		for(int pi = 0; pi < parityBits; pi++) {
			int skip = (int) Math.pow(2, pi);
			int val = 0;
			for(int i = (skip-1); i < message.length; i = i + skip) {
				for(int j = 0; j < skip && i < message.length; j++) {
					val = val + Character.getNumericValue(message[i]);
					i++;
				}
			}
			if( val % 2 != 0) {	
				out = out + (int)Math.pow(2, pi);
			}
		}
		return out;
	}
	
	//Removes the parity bits of the encoded string.
	//@param parityBits The number of expected parity bits.
	//@param message char[] of an encoded bit pattern.
	//@return char[] of the decoded bit pattern.
	public char[] removeParityBits(int parityBits, char[] message)
	{
		char[] out = new char[message.length-parityBits];
		int pindex = 2, nxtpwr = (int) Math.pow(2, pindex);
		int oi = 0;
		for(int i = 2; i < message.length; i++)
		{
			if(i+1 == nxtpwr) {
				nxtpwr = (int)Math.pow(2, ++pindex);
			} else { 
				out[oi] = message[i];
				++oi;
			}
		}
		return out;
	}
	/*	Decodes the string with detection and error correction
	*@param s The string to decode
	*@return an object array: index 0: boolean: false = no error detected, true = error detected
	*						  index 1: the message, error corrected, and without parity bits.
	*						  index 2: Null if no error is found, if an error is found, the input string,
	*								   used for error correction when permanent faults occur.
	*/
	
	public Object[] deCode(String s) {
		boolean error = false;
		//retrieve the bit pattern of the input string
		char[] message = getBitPattern(s, true);
				
		//Calculating the number of parity bits needed for the string
		int parityBits = calculateDecodingParityBits(message.length);
		
		//error detection and correction
		//Calculation of parity bit values
		int pv = calculateParityValues(parityBits,message);
		
		//If a fault was found, correct it
		if(pv > 0) {
			error = true;
			message = correctBitFlip(pv-1,message);
		}
		//Remove parity bits and build output string
		message = removeParityBits(parityBits, message);

		Object[] out;
		if(!error) {
		out = new Object[]{error, toASCII(message)};
		} else { out = new Object[]{error, toASCII(message),s}; }
		return out;
	}

	//Detect errors in an encoded string, will return the correct parity value
	//FIXME needed?
	public int detectErrorInString(String s)
	{
		//retrieve the bit pattern of the input string
		char[] message = getBitPattern(s, true);
		
		//Calculating the number of parity bits needed for the string
		int parityBits = calculateDecodingParityBits(message.length);
		
		//error detection and correction
		//Calculation of parity bit values
		return calculateParityValues(parityBits,message);
	}
		
	//Corrects a single bit flip in a char array.
	public char[] correctBitFlip(int pv,char[] message)
	{
		if(message[pv] == '1') {
			message[pv] = '0';
		} else { message[pv] = '1'; }
		return message;
	}
	
	//Correct a bit flip in a string.
	public String correctBitFlip(int pv, String block) {
		//retrieve the bit pattern of the input string
		char[] message = getBitPattern(block, true);
		
		message = correctBitFlip(pv-1,message);
		
		return toASCII(message);
	}

	//Converts a char array(of bits) to a string.
	public String toASCII(char[] s)
	{
		String ret = "";
		int chars;

		if(s.length % byteLength != 0) {
			chars = s.length/byteLength +1;
		} else { chars = s.length/byteLength; }

		for(int i = 1;i <= chars; i++) {
			String sb = "";
			if(i == chars) {
				for(int j = (i-1)*byteLength; j < s.length; j++) {
					sb = s[j] + sb;
				}
				//last char
			} else {
				for(int j = (i-1)*byteLength; j < (i*byteLength); j++) {
					sb = s[j] + sb;
				}
			}
			ret = (char) Integer.parseInt(sb.toString(), 2) + ret;	
		}
		return ret.toString();
	}

	//Returns the reverse order bit pattern of a string, why? because..thats why.
	//In short it is kind of hard to predict the number of zeroes in front of a given bit pattern, if you work in opposite reading order,
	//who would have thought...
	private char[] getBitPattern(String s, boolean encoded)
	{
		String temp = "";
		//Get byte array of the message, convert to bit string
		if(!encoded) {
			for(char b:  s.toCharArray()) {	
				String bin = Integer.toBinaryString(b);
				if(bin.length() < byteLength) {
					temp = temp + String.format("%0"+(byteLength-bin.length()+"d"+"%s"), 0, bin); }
					else {
						temp = temp + bin;
					}
//				temp = temp + String.format("%0"+byteLength+"d", Integer.parseInt(Integer.toBinaryString(b)));
			}
		}
		else {
			boolean first = true;
			for(char b: s.toCharArray()) {
				String bin = Integer.toBinaryString(b);
				if(first)
				{ temp = temp + Integer.toBinaryString(b); first = false; }
				else { 
					if(bin.length() < byteLength) {
						temp = temp + String.format("%0"+(byteLength-bin.length()+"d"+"%s"), 0, bin); }
						else {
							temp = temp + bin;
						}
					//temp = temp + String.format("%0"+byteLength+"d", Integer.parseInt(Integer.toBinaryString(b))); }
				}
			}
		}
		return reverseOrder(temp.toCharArray());
	}

	//Reverse the order of a char array.
	private char[] reverseOrder(char[] s) {
		char[] out = new char[s.length];
		for(int i = 0; i < s.length; i++) {
			out[i] = s[(s.length-i)-1];
		}
		return out;
	}
	
	//Calculate the amount of parity bits in a bit pattern
	//@parameter length length of the bit pattern
	public int calculateDecodingParityBits(int length) {
		int parityBits = 0;
		while((Math.pow(2, parityBits)-1) < length) {
			parityBits++;
		}
		return parityBits;
	}
	
	public int calculateEncodingParityBits(int length) {
		int parityBits = 0;
		while((Math.pow(2, parityBits)-parityBits-1) < length) {
			parityBits++;
		}
		return parityBits;
	}
}